// test_app.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>

#define _GNU_SOURCE 
#include <fcntl.h>

#define FILE_NAME "test_file.dat"
#define FILE_SIZE (128 * 1024 * 1024) 
#define PAGE_SIZE 4096

// Function to create a large file
void create_test_file() {
    int fd = open(FILE_NAME, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        exit(1);
    }
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        exit(1);
    }
    close(fd);
    printf("Created test file '%s' of size %ld MB\n", FILE_NAME, (long)(FILE_SIZE / (1024*1024)));
}

// Access memory sequentially
void sequential_access(char *map) {
    printf("Starting sequential access test...\n");
    volatile char sink;
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    for (size_t i = 0; i < FILE_SIZE; i += PAGE_SIZE) {
        sink = map[i];
    }
    (void)sink; // V2.2: Suppress "unused variable" warning

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("Sequential access finished in %.4f seconds.\n", elapsed);
}

// Access memory with a fixed stride
void strided_access(char *map, size_t stride) {
    printf("Starting strided access test (stride: %zu bytes)...\n", stride);
    volatile char sink;
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    for (size_t i = 0; i < FILE_SIZE; i += stride) {
        sink = map[i];
    }
    (void)sink; // V2.2: Suppress "unused variable" warning

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("Strided access finished in %.4f seconds.\n", elapsed);
}


int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <mode>\n", argv[0]);
        fprintf(stderr, "Modes:\n");
        fprintf(stderr, "  seq       - Sequential access\n");
        fprintf(stderr, "  stride    - Strided access (default stride: 65536)\n");
        return 1;
    }

    create_test_file();

    int fd = open(FILE_NAME, O_RDONLY);
    if (fd == -1) {
        perror("open for mmap");
        exit(1);
    }
    
    if (posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM) != 0) {
        perror("posix_fadvise(RANDOM) failed");
    } else {
        printf("Hinted to kernel to disable readahead (POSIX_FADV_RANDOM).\n");
    }

    char *map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(1);
    }

    printf("My PID is: %d. Run the prefetcher with this PID.\n", getpid());
    printf("Press Enter to start the test...");
    getchar();

    printf("Attempting to drop file caches...\n");
    sync();
    int drop_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
    if (drop_fd != -1) {
        if (write(drop_fd, "1", 1) == -1) {
            perror("write to drop_caches");
        }
        close(drop_fd);
    } else {
        printf("Warning: Could not drop file caches. Results may vary.\n");
    }
    sleep(1); 

    if (strcmp(argv[1], "seq") == 0) {
        sequential_access(map);
    } else if (strcmp(argv[1], "stride") == 0) {
        size_t stride = (argc > 2) ? atoi(argv[2]) : 65536;
        strided_access(map, stride);
    } else {
        fprintf(stderr, "Unknown mode: %s\n", argv[1]);
    }

    munmap(map, FILE_SIZE);

    if (posix_fadvise(fd, 0, 0, POSIX_FADV_NORMAL) != 0) {
        perror("posix_fadvise(NORMAL) failed");
    } else {
        printf("Restored kernel's default readahead behavior (POSIX_FADV_NORMAL).\n");
    }

    close(fd);
    return 0;
}